/*
 * Copyright 2012 Gayan Perera
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package gap.maven.script.annotation;

import gap.maven.script.components.api.ArtifactFile;
import gap.maven.script.components.api.ArtifactFileFactory;
import gap.maven.script.components.api.POMArtifactFile;
import gap.maven.script.components.api.Require;
import gap.maven.script.components.api.ScriptContext;
import groovy.lang.GroovyClassLoader;

import java.util.List;

import org.apache.maven.plugin.logging.Log;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.messages.SimpleMessage;

/**
 * This compilation customizer will process all {@link Require} annotations and
 * add the required dependencies into the class path. An instance of this class
 * will be added to the compilation configuration using
 * {@link CompilerConfiguration#addCompilationCustomizers(CompilationCustomizer...)}
 * into the {@link CompilePhase#CONVERSION}.
 * 
 * @author gayan
 */
public final class DependencyFetcher extends CompilationCustomizer {
    private final ScriptContext context;
    private final Log log;

    /**
     * Creates a new instance of DependencyFetcher with the given
     * {@link ScriptContext} and {@link Log}.
     * 
     * @param context
     *            script context which contains the artifact file factory
     *            instance which is used to resolve maven artifacts.
     * @param log
     *            maven logger which is used to log.
     */
    public DependencyFetcher(ScriptContext context, Log log) {
        super(CompilePhase.CONVERSION);
        this.context = context;
        this.log = log;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
     * #call(org.codehaus.groovy.control.SourceUnit,
     * org.codehaus.groovy.classgen.GeneratorContext,
     * org.codehaus.groovy.ast.ClassNode)
     */
    @Override
    public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
            throws CompilationFailedException {
        final List<ImportNode> imports = source.getAST().getImports();

        for (ImportNode importNode : imports) {
            final List<AnnotationNode> annotations = importNode.getAnnotations();
            for (AnnotationNode annotation : annotations) {
                if ("Require".equals(annotation.getClassNode().getName())) {

                    log.debug("Found a @Require annotation.");
                    processRequireAnnotations(annotation, source);
                }
            }
        }

    }

    /**
     * Process the given required annotation and add the maven artifact in the
     * class path.
     * 
     * @param annotation
     *            annotation to process.
     * @param source
     *            source unit which is currently under compilation.
     */
    private void processRequireAnnotations(AnnotationNode annotation, SourceUnit source) {

        final ArtifactFileFactory artifactFactory = context.lookup(ArtifactFileFactory.SCRIPT_CONTEXT_KEY);

        final String groupId = readAnnotation(annotation.getMember("groupId"));
        final String artifactId = readAnnotation(annotation.getMember("artifactId"));
        final String version = readAnnotation(annotation.getMember("version"));
        final String type = readAnnotation(annotation.getMember("type"));
        final String classifier = readAnnotation(annotation.getMember("classifier"));

        ArtifactFile file;
        if ((classifier != null) && (classifier.length() != 0)) {
            file = artifactFactory.createArtifactFile(groupId, artifactId, version, type, classifier);
        } else {
            file = artifactFactory.createArtifactFile(groupId, artifactId, version, type, classifier);
        }

        if (file.download()) {
            try {
                source.getClassLoader().addClasspath(file.getFile().getAbsolutePath());

                if (log.isDebugEnabled()) {
                    log.debug("Artifact [" + file.toString() + "] added to classpath.");
                }

                log.debug("Resolving transitive dependencies.");
                resolveTransitiveDependencies(file.getPOM(), source.getClassLoader());

            } catch (Exception e) {
                reportError(e, source);
            }
        } else {
            reportError("Artifact not found for [" + file.toString() + "]", source);
        }
    }

    private void reportError(String message, SourceUnit source) {
        source.getErrorCollector().addFatalError(new SimpleMessage(message, source));
    }

    private void reportError(Exception e, SourceUnit source) {
        source.getErrorCollector().addException(e, source);
    }

    private String readAnnotation(Expression annotationMember) {
        return (annotationMember != null) ? annotationMember.getText() : "";
    }

    private void resolveTransitiveDependencies(POMArtifactFile pom, GroovyClassLoader classLoader) {
        if (pom.download()) {
            List<ArtifactFile> dependecyArtifactFiles = pom.getDependecyArtifactFiles();
            for (ArtifactFile artifactFile : dependecyArtifactFiles) {
                if (artifactFile.download()) {
                    classLoader.addClasspath(artifactFile.getFile().getAbsolutePath());
                    if (log.isDebugEnabled()) {
                        log.debug("Artifact [" + artifactFile.toString() + "] added to classpath.");
                    }
                } else {
                    log.error("Dependency failed to be added into classpath : " + artifactFile.toString());
                }
            }
        }
    }
}
